/*
 * Copyright © 2004-2014 chenYuan. All rights reserved.
 * @Website:wwww.jspx.net
 * @Mail:39793751@qq.com
 * @author: chenYuan , 陈原
 * @License: Jspx.net Framework Code is open source (LGPL)，Jspx.net Framework 使用LGPL 开源授权协议发布。
 * @jvm:jdk1.6+  x86/amd64
 *
 */
package com.jspx.sober.config;

import com.jspx.boot.EnvFactory;
import org.slf4j.Logger;
import com.jspx.boot.environment.Environment;
import com.jspx.boot.environment.EnvironmentTemplate;
import org.slf4j.LoggerFactory;
import com.jspx.datasource.JRWPoolDataSource;
import com.jspx.io.AbstractRead;
import com.jspx.io.AutoReadTextFile;
import com.jspx.sioc.BeanFactory;
import com.jspx.sober.*;
import com.jspx.sober.transaction.JTATransaction;
import com.jspx.sober.transaction.JDBCTransaction;
import com.jspx.sober.transaction.TransactionManager;
import com.jspx.sober.transaction.AbstractTransaction;
import com.jspx.sober.dialect.Dialect;
import com.jspx.sober.dialect.DialectFactory;
import com.jspx.sober.util.AnnotationUtil;
import com.jspx.sober.util.JdbcUtil;
import com.jspx.utils.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.UserTransaction;
import java.io.File;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.DatabaseMetaData;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User:chenYuan (mail:39793751@qq.com)
 * Date: 2007-1-12
 * Time: 9:40:31
 * com.jspx.sober.config.SoberMappingBean
 * 如果使用JTA事务调用,需要注意几点:
 * 1.使用XA数据源,一般数据库驱动里边有例如,postgresql 为 org.postgresql.xa.PGXADataSource
 * 2.数据源使用JNDI 数据源
 * 3.事务也使用JNDI 事务,userTransaction
 */

public class SoberMappingBean implements SoberFactory {

    //日志
    final static private Logger log = LoggerFactory.getLogger(SoberMappingBean.class);
    private static BeanFactory beanFactory = EnvFactory.getBeanFactory();
    //事务管理器
    private static TransactionManager transactionManager = TransactionManager.getInstance();
    //sql映射表
    private static Map<String, SQLRoom> SQLMap = new HashMap<String, SQLRoom>();
    //表结果映射
    private static Map<String, TableModels> tableMap = new HashMap<String, TableModels>();
    //整合服务器JNDI接口
    private Context context = null;
    //一次最多查询行数，避免out memory 
    private int maxRows = 10000;
    //jdbc fetch 大小 
    private int fetchSize = 20;
    //数据源
    private DataSource dataSource;
    //显示SQL
    private boolean showsql = true;
    //自动提交
    private boolean autoCommit = true;
    //事务级别
    private int transactionIsolation = Connection.TRANSACTION_NONE;
    //超时设置
    private int transactionTimeout = 30000; //默认30秒超时
    //JTA 事务调用
    private String userTransaction = null;
    //jdni数据源绑定
    private String dataSourceLoop = null;
    //Sioc数据库名
    private String databaseName;
    //保存的时候是否要验证
    private boolean valid = false;
    //判断是否为JTA方式事务
    private boolean jta = false;
    //事务线程池,满足多线程多事务高并发使用
    private boolean useCache = true;


    public boolean isUseCache() {
        return useCache;
    }

    public void setUseCache(boolean useCache) {
        this.useCache = useCache;
    }

    //载入cache
    private String cacheName = "soberCache";

    public String getCacheName() {
        return cacheName;
    }

    public void setCacheName(String cacheName) {
        this.cacheName = cacheName;
        try {
            beanFactory.getBean(this.cacheName, "cache");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("cacheName Cache 初始化错误", e);
        }
    }

    //状态缓存，保存锁定等信息，主要是为了分布式缓存
    private String stateCache = "stateCache";

    public String getStateCache() {
        return stateCache;
    }

    public void setStateCache(String stateCache) {
        this.stateCache = stateCache;
        try {
            beanFactory.getBean(this.stateCache, "cache");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("stateCache Cache 初始化错误", e);
        }
    }

    public SoberMappingBean() {
        try {
            context = new InitialContext();
        } catch (NamingException e) {
            log.error("JNDI 初始化错误",e);
        }
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    public boolean isJta() {
        return jta;
    }

    public int getMaxRows() {
        return maxRows;
    }

    public void setMaxRows(int maxRows)
    {
        this.maxRows = maxRows;
    }

    public int getTransactionTimeout() {
        return transactionTimeout;
    }

    public void setTransactionTimeout(int transactionTimeout) {
        this.transactionTimeout = transactionTimeout;
    }

    public boolean isValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

    public int getTransactionIsolation() {
        return transactionIsolation;
    }

    public void setTransactionIsolation(int transactionIsolation) {
        this.transactionIsolation = transactionIsolation;
    }

    public boolean isAutoCommit() {
        return autoCommit;
    }

    public void setAutoCommit(boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    public boolean isShowsql() {
        return showsql;
    }

    public void setShowsql(boolean showsql) {
        this.showsql = showsql;
    }

    public int getFetchSize() {
        return fetchSize;
    }

    public void setFetchSize(int fetchSize) {
        this.fetchSize = fetchSize;
    }

    public String getDatabaseName() {
        return databaseName;
    }

    public Dialect getDialect() {
        return dialect;
    }

    private Dialect dialect = null;

    public DataSource getDataSource() {
        return dataSource;
    }
    /**
     * JTA方式的事务接口,JNDI 方式得到到，必须在服务器里边先配置好。
     *
     * @param userTransaction 设置事务方式
     * @throws Exception 异常
     */
    public void setUserTransaction(String userTransaction) throws Exception {
        this.userTransaction = userTransaction;
        try {
            UserTransaction userTransaction1 = (UserTransaction) context.lookup(userTransaction);
            jta = userTransaction1 != null;
        } catch (Exception ex) {
            log.info("DataSource Rollback is JTATransaction",ex);
            jta = false;
        }
        if (jta) {
            log.info("DataSource Rollback is JTATransaction");
        } else {
            log.info("DataSource Rollback is JDBCTransaction");
        }
    }

    /**
     * @param dataSourceLoop JNDI 设置数源，方便和服务器整合
     * @throws Exception
     */
    public void setDataSource(String dataSourceLoop) throws Exception {
        this.dataSourceLoop = dataSourceLoop;
        checkConnection(getConnection(SoberEnv.ReadWrite, SoberEnv.notTransaction));
    }

    /**
     * @param dataSource 普通数据源
     * @throws Exception
     */
    public void setDataSource(DataSource dataSource) throws Exception {
        jta = false;
        this.dataSource = dataSource;
        checkConnection(getConnection(SoberEnv.ReadWrite, SoberEnv.notTransaction));
    }

    /**
     * @param dataSource 直接使用XA数据源,一般要配合JTA事务
     * @throws Exception
     */
    public void setDataSource(XADataSource dataSource) throws Exception {
        jta = true;
        this.dataSource = new XADataSourceProxy(dataSource);
        checkConnection(getConnection(SoberEnv.ReadWrite, SoberEnv.notTransaction));
    }

    /**
     *
     * @param soberSupport 创建一个事务，如果事务已经存在那么就返回已经存在的事务
     * @return 事务对象
     * @throws SQLException  sql 异常
     * @throws NamingException  命名
     */
    public AbstractTransaction createTransaction(SoberSupport soberSupport) throws SQLException {
        AbstractTransaction trans = transactionManager.get(soberSupport.getTid());
        if (trans != null && !trans.isClosed()) return trans;
        Connection connection = getConnection(SoberEnv.ReadWrite, SoberEnv.notTransaction);
        if (jta) {
            JTATransaction transaction = new JTATransaction(soberSupport);
            transaction.setTimeout(transactionTimeout);
            try {
                transaction.setUserTransaction((UserTransaction) context.lookup(userTransaction));
            } catch (NamingException e) {
                log.error("create transaction error:",e);
                e.printStackTrace();
            }
            transaction.setConnection(connection);
            transactionManager.add(transaction);
            return transaction;
        } else {
            JDBCTransaction transaction = new JDBCTransaction(soberSupport);
            transaction.setTimeout(transactionTimeout);
            transaction.setSupportsSavePoints(dialect.supportsSavePoints());
            transaction.setConnection(connection);
            transactionManager.add(transaction);
            return transaction;
        }

    }

    /**
     * 检测连接池配置是否正确,初始化使用，只运行一次
     *
     * @param conn  链接
     * @throws Exception
     */
    private void checkConnection(Connection conn) throws Exception {
        log.info("Connection is " + conn);
        try {
            if (conn == null || conn.isClosed()) {
                log.error("database dataSource not get JDBC Connection，数据库配置错误");
                throw new SQLException("database dataSource not get JDBC Connection,数据库配置错误");
            }
            if (StringUtil.isNULL(databaseName) || Environment.auto.equalsIgnoreCase(databaseName)) {
                this.databaseName = JdbcUtil.getDatabaseName(conn);
                this.dialect = DialectFactory.createDialect(databaseName);
            } else {
                this.dialect = DialectFactory.createDialect(databaseName);
            }
            DatabaseMetaData databaseMetaData = conn.getMetaData();
            this.dialect.setSupportsSavePoints(databaseMetaData.supportsSavepoints());
            this.dialect.setSupportsGetGeneratedKeys(databaseMetaData.supportsGetGeneratedKeys());
        } finally {
            closeConnection(conn,false);
        }
    }

    /**
     *
     * @param type 读写分离 0 ReadWrite 1 ReadOnly 2 WriteOnly
     * @param tid  事务连接ID
     * @return 通过各种方式得到连接，包括XA连接
     * @throws SQLException  sql异常
     * @throws NamingException  命名空间异常
     */
    public Connection getConnection(final int type, int tid) throws SQLException {
        //判断事物是否超时
        if (!transactionManager.isEmpty()) transactionManager.checkTransactionOvertime();

        if (tid != SoberEnv.notTransaction) {
            AbstractTransaction trans = transactionManager.get(tid);
            if (trans != null &&!trans.isClosed() && trans.getConnection()!=null)
                return trans.getConnection();
        }
        Connection conn = null;
        if (dataSource != null) {
            if (dataSource instanceof JRWPoolDataSource) {
                JRWPoolDataSource rwDataSource = (JRWPoolDataSource) dataSource;
                conn = rwDataSource.getConnection(type);
            } else {
                conn = dataSource.getConnection();
            }
        } else {
            Object xDataSource = null;
            try {
                xDataSource = context.lookup(dataSourceLoop);
                if (xDataSource instanceof XADataSource) {
                    XADataSource dataSource = (XADataSource) xDataSource;
                    conn = dataSource.getXAConnection().getConnection();
                } else if (xDataSource instanceof JRWPoolDataSource) {
                    JRWPoolDataSource rwDataSource = (JRWPoolDataSource) xDataSource;
                    conn = rwDataSource.getConnection(type);
                } else if (xDataSource instanceof DataSource) {
                    DataSource dataSource = (DataSource) xDataSource;
                    conn = dataSource.getConnection();
                }  else {
                    throw new NamingException("JNDI dataSource Loop " + dataSourceLoop + " is NULL");
                }
            } catch (NamingException e) {
                e.printStackTrace();
                log.error("getConnection transaction error:",e);
            }

        }
        if (!autoCommit && conn != null) {
            conn.setAutoCommit(autoCommit);
        }

        if (conn!=null&&transactionIsolation > Connection.TRANSACTION_NONE) {
            conn.setTransactionIsolation(transactionIsolation);
        }
        return conn;
    }



    /**
     * @param conn 关闭连接
     */
    public void closeConnection(Connection conn, boolean release) {
        if (conn == null) return;
        JdbcUtil.closeConnection(conn, release);
    }

    /**
     *
     * @param cla SQL房间
     * @return  sql 空间
     */
    public SQLRoom getSQLRoom(Class cla) {
        return SQLMap.get(cla.getClass().getName());
    }
    /**
     *
     * @param namespace 得到命名空间的SQL
     * @return  sql空间
     */
    public SQLRoom getSQLRoom(String namespace) {
        return SQLMap.get(namespace);
    }


    public void setMappingResources(String[] strings) throws Exception {
        if (!SQLMap.isEmpty()) return;
        EnvironmentTemplate envTemplate = EnvFactory.getEnvironmentTemplate();
        if (strings != null) {
            String[] files = new String[0];
            for (String file : strings) {
                if (file.contains("*") || file.contains("?") || file.contains("#")) {
                    File[] findFiles = FileUtil.getPatternFiles(envTemplate.getString(Environment.defaultPath), file);
                    for (File f : findFiles) {
                        if (FileUtil.isFileExist(f.getAbsolutePath())) {
                            files = ArrayUtil.add(files, f.getName());
                        }
                    }
                } else {
                    files = ArrayUtil.add(files, file);
                }
            }
            for (String file : files) {
                if (StringUtil.isNULL(file)) continue;
                String fileName = file;
                URL url = ClassUtil.getResource(file);
                if (url == null) {
                    url = ClassUtil.getResource(envTemplate.getString(Environment.defaultPath) + file);
                }
                if (url == null && (file.startsWith("http") || file.startsWith("file") || file.startsWith("ftp"))) {
                    url = new URL(file);
                }
                if (url == null) {
                    if (FileUtil.isFileExist(file)) {
                        fileName = file;
                    } else if (FileUtil.isFileExist(envTemplate.getString(Environment.defaultPath) + file)) {
                        fileName = envTemplate.getString(Environment.defaultPath) + file;
                    }
                } else {
                    fileName = url.getPath();
                }
                if (!FileUtil.isFileExist(fileName)) {
                    throw new Exception("not find file " + file);
                }
                log.info("loading sql map file:" + fileName);
                AbstractRead abstractRead = new AutoReadTextFile();
                abstractRead.setEncode(envTemplate.getString(Environment.encode, Environment.defaultEncode));
                abstractRead.setFile(fileName);
                String xmlString = abstractRead.getContent();
                xmlString = xmlString.substring(xmlString.indexOf("<"), xmlString.length());
                SoberSQLMapXML soberSQLMapXML = new SoberSQLMapXML(SQLMap);
                try {
                    if (XMLUtil.parseXmlString(soberSQLMapXML, xmlString))
                        SQLMap.putAll(soberSQLMapXML.getSQLMap());
                } catch (Exception e) {
                    log.error("config file=" + file,e);
                }
            }
        }
    }

    /**
     * 得到表结构,如果数据库中不存在表，就创建表
     *
     * @param cla  类
     * @param soberSupport  支持对象
     * @return 得到表结构
     */

    public TableModels getTableModels(Class cla,final SoberSupport soberSupport) {
        TableModels soberTable = tableMap.get(cla.getName());
        if (soberTable != null)
            return soberTable;

        soberTable = AnnotationUtil.getSoberTable(cla);
        if (soberTable == null) return null;
        tableMap.put(cla.getName(), soberTable);
        String sql = null;
        try {
            if (soberTable.isCreate() && !soberSupport.tableExists(cla) )
            {
                sql = soberSupport.getCreateTableSQL(cla);
               soberSupport .execute(sql);
            }
        } catch (Exception e) {
            log.error("ERROR:auto create table 自动创建表错误:"+ sql + " table:"+ soberTable.toString() + " 原因:没有得到连接或者和数据库不兼容", e);
            e.printStackTrace();
        }
        return soberTable;
    }

    public void clear() {
        tableMap.clear();
    }

}